package CHeaderParser;

use Signature;
use CArg;
use CPreprocess;
use CContext;
use CClass;
use CConstructor;

use TinyMessage;

my $CPP_OPEN_BRACKET	  = '{';
my $CPP_CLOSE_BRACKET	  = '}';
my $CPP_INCLUDE   = '#include "([^"]+)"';
my $CPP_NAMESPACE = 'namespace ([^{]+)';

my $SUB				= 'SUB < ([^.]*) > ([^;]*)\(\)';
my $IOC				= 'IOC < ([^.]*) > ([^;]*)';

my $BIND_SINGLETON  = 'BIND < ([^,].*),\s*SINGLETON < (.*) > > ([^;]*)\(\);';
my $BIND_DIRECT     = 'BIND < ([^,].*),\s*DIRECT < (.*) > > ([^;]*)\(\);';
my $BIND_CUSTOM	    = 'BIND < ([^,].*),\s*(.*) > ([^;]*)\(\);';

my $CPP_CLASS     	= 'class ([^{:;]+)[^;]*$';
my $CPP_BASE_CLASS  = 'public ([^,])';
my $CPP_NOT_VISIBLE = '(private|protected)';
my $CPP_VISIBLE 	= '(public)';

my $canonize = sub {
	my @values = @_;
	grep { s/(^\s+|\s+$)//g; }  @values;
	
	if (@values == 1) {
		return $values[0];
	}
	
	return @values;
};

sub parseFile {
	my ($file, $routines) = @_;

	open(my $f, "<$file")
    	|| fatal 'F_C_HEADER_PARSER_OPEN', $file, $!;
    	
    parse($f, $routines, $file);
    	
    close $f;
}

sub parse {
	my ($f, $routines, $source) = @_;
	
	$source = '$f' if (!$source);
	
	my @canonized = CPreprocess::canonize($f);
	
	my $context = new CContext();
	
    foreach $_ (@canonized) {
    	s/(\r|\n)//;
    	s/(^\s+|\s+$)//g;
    	
    	my $isClassContext = $context->classContext();
    	my $class = $isClassContext;
    	
    	if (m/$CPP_OPEN_BRACKET/) {
    		$context = new CContext($context);
    	}
    	
    	if (m/$CPP_NOT_VISIBLE/) {
    		$context->setVisibleScope(0);
    	}
    	
    	if (m/$CPP_VISIBLE/) {
    		$context->setVisibleScope(1);
    	}
    	
    	if (m/$CPP_NAMESPACE/g) {
    		$context->setNamespace($canonize->($1)); 
    	}

		if (m/$CPP_CLASS/) {
			#Nested classes not supported
			if (!$isClassContext) { 
				my $className = $canonize->($1);
				my @base = m/$CPP_BASE_CLASS/g;
				my @namespace = $context->fullNamespace();
			
				$context->setClass(new CClass(class_sig($className, \@namespace), $source));
				$context->setVisibleScope(0);
			
    			if ($routines->{class}) {
    				&{ $routines->{class} }($context);
    			}
			}
    	}
    	
    	if ($isClassContext) {
    		my $className = $class->simpleName();
    		if (m/^[^~]*$className *\(([^)]*)\)/) {
    			if ($1) {
    				my @namespace = $context->fullNamespace();
    				$class->addConstructor(new CConstructor(ctor_sig(parse_cargs($1, \@namespace)), $context->visibleScope()));
    			} else {
    				$class->addConstructor(new CConstructor(ctor_sig(()), $context->visibleScope()));
    			}
    		}
    	}
    	
    	if (m/$CPP_INCLUDE/) {
    		if ($routines->{include}) {
    			&{ $routines->{include} }($canonize->($1), $context);
    		}
    		next;
    	}
    	
    	if (m/$BIND_SINGLETON/) {
    		if ($routines->{bind_singleton}) {
    			my @namespace = $context->fullNamespace();
    			my ($class, $impl, $method) = $canonize->($1, $2, $3);
	    		$routines->{bind_singleton}->($context, 
	    			class_sig($class, \@namespace), 
	    			class_sig($impl, \@namespace), 
	    			$method);
    		}
    	} elsif (m/$BIND_DIRECT/) {
    		if ($routines->{bind_direct}) {
    			my @namespace = $context->fullNamespace();
    			my ($class, $impl, $method) = $canonize->($1, $2, $3);
	    		$routines->{bind_direct}->($context, 
					class_sig($class, \@namespace), 
	    			class_sig($impl, \@namespace),
		    		$method);
    		}
    	} elsif (m/$BIND_CUSTOM/) {
    		if ($routines->{bind_custom}) {
    			my @namespace = $context->fullNamespace();
    			my ($class, $impl, $method) = $canonize->($1, $2, $3);
	    		$routines->{bind_custom}->($context, 
					class_sig($class, \@namespace), 
	    			class_sig($impl, \@namespace), 
	    			$method);
    		}
    	}
    	
    	if (m/$SUB/) {
    		 if ($routines->{sub_module}) {
    			my @namespace = $context->fullNamespace();
    			my ($module, $method) = $canonize->($1, $2);
	    		$routines->{sub_module}->(
	    			$context,
					class_sig($module, \@namespace), 
	    			$method);
    		}
    	}
    	
    	if (m/$IOC/) {
    		if ($routines->{ioc}) {
    			my @namespace = $context->fullNamespace();
    			my ($module, $variable) = $canonize->($1, $2);
    			$routines->{ioc}->(
    				$context,
    				$module,
    				$variable);
    		}
    	}
    	
    	if (m/$CPP_CLOSE_BRACKET/) {
    		$context = $context->{parent};
    	}
    }
}

sub parse_cargs {
	my ($argline, $namespace) = @_;
	
	my @cargs = ();
	
	if (!$argline) {
		return @cargs;
	}
	
	my @args = split(',', $argline);
	
	foreach my $arg (@args) {
		$arg =~ s/(^\s+|\s+$)//g;
		$arg =~ s/([^\s&*]+)$//g;
		
		if ($arg =~ s/\s*\*\s*$//) {
			push(@cargs, new CArg(arg_sig($arg, $namespace, '*')));
			next;
		}
		
		if ($arg =~ s/\s*\&\s*$//) {
			push(@cargs, new CArg(arg_sig($arg, $namespace, '&')));
			next;
		}
		
		$arg =~ s/(^\s+|\s+$)//g;
		push(@cargs, new CArg(arg_sig($arg, $namespace, '')));
	}
	
	return @cargs;
}

1;